﻿using HardcoreGame.Content.Buffs;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Terraria;
using Terraria.Audio;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;

namespace HardcoreGame.Common.Players
{
    public enum FractureLocation
    {
        Head,
        LeftArm,
        RightArm,
        Torso,
        LeftLeg,
        RightLeg
    }

    public class Fracture(FractureLocation location, float severity)
    {
        public FractureLocation Location { get; set; } = location;
        public float Severity { get; set; } = severity;
        public int TimeSinceInjury { get; set; } = 0;
    }

    public class FracturePlayer : ModPlayer
    {
        public readonly Dictionary<FractureLocation, float> _partHealth = new()
        {
            { FractureLocation.Head, 1f },
            { FractureLocation.Torso, 1f },
            { FractureLocation.LeftArm, 1f },
            { FractureLocation.RightArm, 1f },
            { FractureLocation.LeftLeg, 1f },
            { FractureLocation.RightLeg, 1f }
        };

        public List<Fracture> ActiveFractures { get; private set; } = [];
        public bool ArmsDisabled => _partHealth[FractureLocation.LeftArm] <= 0f && _partHealth[FractureLocation.RightArm] <= 0f;
        public bool LegsDisabled => _partHealth[FractureLocation.LeftLeg] <= 0f && _partHealth[FractureLocation.RightLeg] <= 0f;
        public float GetPartHealth(FractureLocation location) => _partHealth[location];
        public override void PostUpdate()
        {
            if (_partHealth[FractureLocation.Head] <= 0 || _partHealth[FractureLocation.Torso] <= 0)
            {
#pragma warning disable CS0618
                Player.KillMe(Terraria.DataStructures.PlayerDeathReason.ByCustomReason($"{Player.name}" + Language.GetTextValue("Mods.HardcoreGame.PlayerDeathReason.3")), 9999, 0);
#pragma warning restore CS0618
                return;
            }
            UpdateFractureEffects();
        }

        private void UpdateFractureEffects()
        {
            if (ArmsDisabled)
            {
                Player.noItems = true;
                Player.delayUseItem = true;
                if (Main.rand.NextBool(120))
                {
                    CombatText.NewText(Player.Hitbox, Color.Red, Language.GetTextValue("Mods.HardcoreGame.Tips.8"), true);
                    SoundEngine.PlaySound(SoundID.Item35 with { Pitch = -0.5f });
                }
            }
            if (LegsDisabled)
            {
                Player.velocity.X *= 0;
                Player.controlLeft = false;
                Player.controlRight = false;
                Player.controlJump = false;
                if (Main.rand.NextBool(120))
                {
                    CombatText.NewText(Player.Hitbox, Color.Red, Language.GetTextValue("Mods.HardcoreGame.Tips.9"), true);
                    SoundEngine.PlaySound(SoundID.Item35 with { Pitch = 0.5f });
                }
            }
            foreach (var fracture in ActiveFractures)
            {
                float severity = 1f - _partHealth[fracture.Location];
                fracture.Severity = severity;

                switch (fracture.Location)
                {
                    case FractureLocation.LeftArm:
                    case FractureLocation.RightArm:
                        UpdateArmEffects(fracture.Location, severity);
                        break;
                    case FractureLocation.LeftLeg:
                    case FractureLocation.RightLeg:
                        UpdateLegEffects(fracture.Location, severity);
                        break;
                }
            }
        }

        private void UpdateArmEffects(FractureLocation location, float severity)
        {
            if (_partHealth[location] > 0f)
            {
                float penalty = severity * 0.6f;
                Player.GetAttackSpeed(DamageClass.Generic) *= 1f - penalty;
                Player.tileSpeed *= 1f - penalty * 0.8f;
            }
        }

        private void UpdateLegEffects(FractureLocation location, float severity)
        {
            if (_partHealth[location] > 0f)
            {
                float penalty = severity * 0.5f;
                Player.moveSpeed *= 1f - penalty;
                if (Main.rand.NextFloat() < severity * 0.1f && Player.velocity.X != 0)
                {
                    Player.velocity.X *= 0.7f;                    
                }
            }
        }

        public void DamagePart(FractureLocation location, float damage)
        {
            if (ActiveFractures.Exists(f => f.Location == location))
            {
                damage *= 1.5f;
            }

            float oldHealth = _partHealth[location];
            _partHealth[location] = MathHelper.Clamp(_partHealth[location] - damage, 0f, 1f);
            if (_partHealth[location] <= 0 && !ActiveFractures.Exists(f => f.Location == location))
            {
                float severity = 1f - _partHealth[location];
                ActiveFractures.Add(new Fracture(location, severity));
                ApplyFractureEffects(location);
                if (location == FractureLocation.LeftArm || location == FractureLocation.RightArm)
                {
                    CombatText.NewText(Player.Hitbox, Color.Orange, Language.GetTextValue("Mods.HardcoreGame.Tips.10"), true);
                    SoundEngine.PlaySound(SoundID.NPCHit4 with { Pitch = -0.3f });
                    if (Main.rand.NextBool(3))
                    {
                        Player.dropItemCheck();
                    }
                }
                else if (location == FractureLocation.LeftLeg || location == FractureLocation.RightLeg)
                {
                    CombatText.NewText(Player.Hitbox, Color.Red, Language.GetTextValue("Mods.HardcoreGame.Tips.11"), true);
                    SoundEngine.PlaySound(SoundID.NPCHit18 with { Pitch = -0.5f });
                }
            }
        }

        public bool HealFracture(FractureLocation location, float healAmount)
        {
            if (!ActiveFractures.Exists(f => f.Location == location))
                return false;

            _partHealth[location] = MathHelper.Clamp(_partHealth[location] + healAmount, 0f, 1f);
            if (_partHealth[location] >= 0.8f) 
            {
                ActiveFractures.RemoveAll(f => f.Location == location);
                RemoveFractureEffects(location);
                if (Main.myPlayer == Player.whoAmI)
                {
                    for (int i = 0; i < 5; i++)
                    {
                        Dust.NewDust(Player.position, Player.width, Player.height,
                                    DustID.BlueTorch, 0, 0, 100, default, 1.5f);
                    }
                }
                return true;
            }
            return true; 
        }
        public override void ModifyHitByNPC(NPC npc, ref Player.HurtModifiers modifiers)
        {
            float damagePercent = modifiers.FinalDamage.Flat / Player.statLifeMax2;
            ProcessInjury(damagePercent, modifiers.HitDirection);
        }

        public override void ModifyHitByProjectile(Projectile proj, ref Player.HurtModifiers modifiers)
        {
            float damagePercent = modifiers.FinalDamage.Flat / Player.statLifeMax2;
            ProcessInjury(damagePercent, proj.direction);
        }

        private void ProcessInjury(float damagePercent, int hitDirection)
        {
            damagePercent = Math.Max(damagePercent, 0.01f);
            float baseChance = MathHelper.Clamp(damagePercent * 3f, 0.1f, 0.8f);

            if (Main.rand.NextFloat() < baseChance)
            {
                var locations = Enum.GetValues(typeof(FractureLocation));
                FractureLocation location;
                if (Player.velocity.Y > 5 && Main.rand.NextBool(3))
                {
                    location = Main.rand.NextBool() ?
                        FractureLocation.LeftLeg : FractureLocation.RightLeg;
                }
                else if (hitDirection == 1 && Main.rand.NextBool(3))
                {
                    location = FractureLocation.RightArm;
                }
                else if (hitDirection == -1 && Main.rand.NextBool(3))
                {
                    location = FractureLocation.LeftArm;
                }
                else
                {
                    location = (FractureLocation)locations.GetValue(Main.rand.Next(locations.Length));
                }
                float actualDamage = MathHelper.Lerp(0.03f, damagePercent * 1.5f, damagePercent);
                DamagePart(location, actualDamage);
            }
            if (Player.velocity.Y > 10)
            {
                float fallDamage = MathHelper.Clamp(Player.velocity.Y / 25f, 0.1f, 0.6f);
                var leg = Main.rand.NextBool() ? FractureLocation.LeftLeg : FractureLocation.RightLeg;
                DamagePart(leg, fallDamage);
            }
        }

        private void ApplyFractureEffects(FractureLocation location)
        {
            int buffType = location switch
            {
                FractureLocation.LeftArm or FractureLocation.RightArm =>
                    ModContent.BuffType<ArmFracture>(),
                FractureLocation.LeftLeg or FractureLocation.RightLeg =>
                    ModContent.BuffType<LegFracture>(),
                _ => -1
            };

            if (buffType != -1)
            {
                Player.AddBuff(buffType, 2);
            }
        }

        private void RemoveFractureEffects(FractureLocation location)
        {
            int buffType = location switch
            {
                FractureLocation.LeftArm or FractureLocation.RightArm =>
                    ModContent.BuffType<ArmFracture>(),
                FractureLocation.LeftLeg or FractureLocation.RightLeg =>
                    ModContent.BuffType<LegFracture>(),
                _ => -1
            };

            if (buffType != -1)
            {
                Player.ClearBuff(buffType);
            }
        }

        public override void SaveData(TagCompound tag)
        {
            var healthData = new TagCompound();
            foreach (var kvp in _partHealth)
            {
                healthData.Add(kvp.Key.ToString(), kvp.Value);
            }
            tag["BodyPartHealth"] = healthData;

            var fractures = new List<string>();
            foreach (var fracture in ActiveFractures)
            {
                fractures.Add($"{fracture.Location}:{fracture.Severity}");
            }
            tag["ActiveFractures"] = fractures;
        }

        public override void LoadData(TagCompound tag)
        {
            if (tag.ContainsKey("BodyPartHealth"))
            {
                var healthData = tag.Get<TagCompound>("BodyPartHealth");
                foreach (var location in Enum.GetValues(typeof(FractureLocation)))
                {
                    string key = location.ToString();
                    if (healthData.ContainsKey(key))
                    {
                        _partHealth[(FractureLocation)location] = healthData.GetFloat(key);
                    }
                }
            }

            ActiveFractures.Clear();
            if (tag.ContainsKey("ActiveFractures"))
            {
                foreach (string fractureData in tag.Get<List<string>>("ActiveFractures"))
                {
                    var parts = fractureData.Split(':');
                    if (Enum.TryParse(parts[0], out FractureLocation location) &&
                        float.TryParse(parts[1], out float severity))
                    {
                        ActiveFractures.Add(new Fracture(location, severity));
                    }
                }
            }
        }
    }
}